package com.icee.myth.common.channelHandler;

import com.icee.myth.utils.LogConsts;
import com.icee.myth.utils.MLogger;
import com.icee.myth.utils.StackTraceUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.*;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.util.CharsetUtil;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;


/**
 * Http服务器处理
 *
 * @author yangyi
 */
public class HttpServerHandler extends ChannelInboundHandlerAdapter {

    public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
    public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
    public static final int HTTP_CACHE_SECONDS = 60;

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_DEBUG, "HttpServerHandler " + StackTraceUtil.getStackTrace(cause));
        ctx.channel().close();
    }

    public static void writeResponse(HttpRequest request, Channel channel, String contentType, String content, ServerCookieEncoder newCookieEncoder) {
        // Decide whether to close the connection or not.
        // 解析Connection首部，判断是否为持久连接
        boolean keepAlive = isKeepAlive(request);

        // Build the response object.
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(content.getBytes(CharsetUtil.UTF_8)));
//        response.content(ChannelBuffers.copiedBuffer(content, CharsetUtil.UTF_8));

//        response.setHeader(CONTENT_TYPE, contentType);
        response.headers().set(CONTENT_TYPE, contentType);
        response.setStatus(HttpResponseStatus.OK);

        // 服务端可以通过location首部将客户端导向某个资源的地址。
        // response.addHeader("Location", uri);
        if (keepAlive) {
            // Add 'Content-Length' header only for a keep-alive connection.
//            response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
            // Add keep alive header as per http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
//            response.setHeader(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }

        // Encode the cookie.
//        String cookieString = request.getHeader(COOKIE);
        // 得到客户端的cookie信息，并再次写到客户端
        String cookieString = request.headers().get(COOKIE);

        if (cookieString != null) {
//            CookieDecoder cookieDecoder = new  CookieDecoder();
            ServerCookieDecoder cookieDecoder = ServerCookieDecoder.STRICT;
            Set<io.netty.handler.codec.http.cookie.Cookie> cookies = cookieDecoder.decode(cookieString);
            if (!cookies.isEmpty()) {
                // Reset the cookies if necessary.
//                CookieEncoder cookieEncoder = new CookieEncoder(true);
                io.netty.handler.codec.http.cookie.ServerCookieEncoder cookieEncoder = ServerCookieEncoder.STRICT;
                for (io.netty.handler.codec.http.cookie.Cookie cookie : cookies) {
                    cookieEncoder.encode(cookie);
                }
//                response.addHeader(SET_COOKIE, cookieEncoder.encode());
                response.headers().add(SET_COOKIE, cookieEncoder.encode());
            }
        } else {
            // Browser sent no cookie.  Add some.
//            response.headers().add(SET_COOKIE, ServerCookieEncoder.STRICT.encode("key1", "value1"));
//            response.headers().add(SET_COOKIE, ServerCookieEncoder.STRICT.encode("key2", "value2"));
        }

        if (newCookieEncoder != null) {
//            response.addHeader(SET_COOKIE, newCookieEncoder.encode());
            response.headers().add(SET_COOKIE, newCookieEncoder.encode());
        }

        // Write the response.
        ChannelFuture future = channel.writeAndFlush(response);
        MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_DEBUG, content);

        // Close the non-keep-alive connection after the write operation is done.
        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    public static int getRemoteIP(Channel channel) {
        InetSocketAddress saddr = (InetSocketAddress) channel.remoteAddress();
        InetAddress inetAddress = saddr.getAddress();
        byte[] bs = inetAddress.getAddress();
        int ip = 0;
        if (bs.length == 4) {
            ip = (bs[0] & 0xFF) << 24 | (bs[1] & 0xFF) << 16 | (bs[2] & 0xFF) << 8 | (bs[3] & 0xFF);
        }
        return ip;
    }

    /**
     * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
     *
     * @param ctx Context
     */
    public static void sendNotModified(ChannelHandlerContext ctx) {
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_MODIFIED);
        setDateHeader(response);

        // Close the connection as soon as the error message is sent.
        ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    public static void showViewPage(HttpRequest request, Channel channel, String viewName, ServerCookieEncoder newCookieEncoder) {
        BufferedReader bufReader = null;
        try {
            //            bufReader = new BufferedReader(new FileReader("views/" + viewName + ".html"));//读中文文件乱码问题
            InputStreamReader isr = new InputStreamReader(new FileInputStream("views/" + viewName + ".html"), "UTF-8");
            bufReader = new BufferedReader(isr);

            StringBuilder buf = new StringBuilder();
            String text;
            while ((text = bufReader.readLine()) != null) {
                buf.append(text).append("\r\n");
            }

            writeResponse(request, channel, "text/html; charset=UTF-8", buf.toString(), newCookieEncoder);
        } catch (IOException ex) {
            Logger.getLogger(HttpServerHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (null != bufReader) {
                try {
                    bufReader.close();
                } catch (Exception ex) {
                    Logger.getLogger(HttpServerHandler.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    /**
     * Sets the Date header for the HTTP response
     *
     * @param response HTTP response
     */
    public static void setDateHeader(HttpResponse response) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        Calendar time = new GregorianCalendar();
//        response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
        response.headers().set(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
    }

    /**
     * Sets the Date and Cache headers for the HTTP Response
     *
     * @param response    HTTP response
     * @param fileToCache file to extract content type
     */
    public static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        // Date header
        Calendar time = new GregorianCalendar();
//        response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
        response.headers().set(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));

        // Add cache headers
        time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
//        response.setHeader(HttpHeaders.Names.EXPIRES, dateFormatter.format(time.getTime()));
//        response.setHeader(HttpHeaders.Names.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
//        response.setHeader(HttpHeaders.Names.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
        response.headers().set(HttpHeaders.Names.EXPIRES, dateFormatter.format(time.getTime()));
        response.headers().set(HttpHeaders.Names.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
        response.headers().set(HttpHeaders.Names.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
    }


    public static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        String context = "Failure: " + status.toString() + "\r\n";
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.wrappedBuffer(context.getBytes(CharsetUtil.UTF_8)));
//        response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
//        response.content();   //ChannelBuffers.copiedBuffer("Failure: " + status.toString() + "\r\n",CharsetUtil.UTF_8));

        // Close the connection as soon as the error message is sent.
        ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    public static void send100Continue(ChannelHandlerContext ctx) {
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
        ctx.channel().write(response);
    }

    public static void handleFileRequest(ChannelHandlerContext ctx, Object msg, final String path, ServerCookieEncoder cookieEncoder, MimetypesFileTypeMap mimeTypesMap) throws Exception {
        FullHttpRequest request = (FullHttpRequest) msg;
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }

        // Cache Validation
        String ifModifiedSince = request.headers().get(HttpHeaders.Names.IF_MODIFIED_SINCE);
        if (ifModifiedSince != null && !ifModifiedSince.equals("")) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
            Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);

            // Only compare up to the second because the datetime format we send to the client does not have milliseconds
            long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
            long fileLastModifiedSeconds = file.lastModified() / 1000;
            if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
                sendNotModified(ctx);
                return;
            }
        }

//        RandomAccessFile raf;
//        try {
//            raf = new RandomAccessFile(file, "r");
//        } catch (FileNotFoundException fnfe) {
//            sendError(ctx, NOT_FOUND);
//            return;
//        }
//        long fileLength = raf.length();
        StringBuffer contents = new StringBuffer();
        BufferedReader reader = null;
        reader = new BufferedReader(new FileReader(file));
        String text = null;
        while ((text = reader.readLine()) != null) {
            contents.append(text);
            contents.append(System.getProperty("line.separator"));
        }
        writeResponse(request, ctx.channel(), mimeTypesMap.getContentType(file), contents.toString(), cookieEncoder);


//        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
//        setContentLength(response, fileLength);
//        setContentTypeHeader(response, file);
//        setDateAndCacheHeaders(response, file);
//
//        Channel ch = ctx.channel();
//
//        // Write the initial line and the header.
//        ch.write(response);
//
//        // Write the content.
//        ChannelFuture writeFuture;
//        if (ch.getPipeline().get(SslHandler.class) != null) {
//            // Cannot use zero-copy with HTTPS.
//            writeFuture = ch.write(new ChunkedFile(raf, 0, fileLength, 8192));
//        } else {
//            // No encryption - use zero-copy.
//            final FileRegion region =
//                    new DefaultFileRegion(raf.getChannel(), 0, fileLength);
//            writeFuture = ch.write(region);
//            writeFuture.addListener(new ChannelFutureProgressListener() {
//
//                public void operationComplete(ChannelFuture future) {
//                    region.releaseExternalResources();
//                }
//
//                public void operationProgressed(
//                        ChannelFuture future, long amount, long current, long total) {
//                    System.out.printf("%s: %d / %d (+%d)%n", path, current, total, amount);
//                }
//            });
//        }
//
//        // Decide whether to close the connection or not.
//        if (!isKeepAlive(request)) {
//            // Close the connection when the whole content is written out.
//            writeFuture.addListener(ChannelFutureListener.CLOSE);
//        }
    }

    /**
     * Sets the content type header for the HTTP Response
     *
     * @param response HTTP response
     * @param file     file to extract content type
     */
//    public static void setContentTypeHeader(HttpResponse response, File file) {
//        response.setHeader(HttpHeaders.Names.CONTENT_TYPE, Manager.INSTANCE.mimeTypesMap.getContentType(file/*.getPath()*/));
//    }
    public static void sendErrorNoPrivilege(HttpRequest request, Channel channel) {
        writeResponse(request, channel, "text/html; charset=UTF-8", "{\"result\":" + -1 + ", \"error\": " + "\"权限不够\"}", null);
    }

}
